﻿/** 
 * Copyright (c) 2001 University of Maryland, College Park, MD 20742, USA
 * Copyright (c) 2005 In4Arch
 *
 *  The MIT License
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of this 
 *  software and associated documentation files (the "Software"), to deal in the Software 
 *  without restriction, including without limitation the rights to use, copy, modify, merge, 
 *  publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 
 *  to whom the Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all copies 
 *  or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 *  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 *  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 *  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 *  OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: Benjamin B. Bederson and Martin Wattenberg 2001 
 * http://www.cs.umd.edu/hcil/treemap-history/index.shtml
 * 
 * Author: moedusa moedusa@gmail.com 2005
 * http://moedusa.name/
 *
 * Author: Nikita Shlyakhov shlyakhov@mail.ru 2006
 **/

 
 import in4arch.alg.treemap.MapModel; 
 import in4arch.alg.treemap.Mappable; 
 import in4arch.alg.treemap.MapItem;
 import in4arch.alg.treemap.Rect; 
 
/**
 * An implementation of MapModel that represents
 * a hierarchical structure. It currently cannot
 * handle structural changes to the tree, since it
 * caches a fair amount of information.
 */
class in4arch.alg.treemap.TreeModel implements MapModel {
	
    private var mapItem:Mappable;
    private var childItems:Array;
    private var cachedTreeItems:Array; // we assume tree structure doesn't change.
    private var cachedLeafModels:Array;
    private var parent:TreeModel;
    private var children:Array = new Array();
    private var sumsChildren:Boolean;
    
    public function TreeModel() {
        this.mapItem = new MapItem();
        this.sumsChildren = true;
    }
    /*
    public TreeModel2(mapItem:Mappable) {
        this.mapItem = mapItem;
    }*/
    
    
    public function setOrder(order:Number):Void {
        mapItem.setOrder(order);
    }
    
    public function getLeafModels(Void):Array {
		
        if (cachedLeafModels != null){
            return cachedLeafModels;
		}
		
        var v:Array = new Array();
        addLeafModels(v);
		
        var n:Number = v.length;
		
        var m:Array = new Array[n];
		
        //v.copyInto(m);
		
        cachedLeafModels = m;
		
        return m;
    }
    
    private function addLeafModels(v:Array):Array{
        if (!hasChildren()) {
            trace("Somehow tried to get child model for leaf!!!");
            return v;
        }
		
        if (!getChild(0).hasChildren()){
			v.push(this);
		}else{
            for (var i = childCount()-1; i >=0; i--){
                getChild(i).addLeafModels(v);
			}
		}
        return v;
    }
    
    public function depth():Number {
        if (parent==null) return 0;
        return 1+parent.depth();
    }
    
    public function _layout(tiling:MapLayout):Void {
        layout(tiling, mapItem.getBounds());
    }
    
    public function layout(tiling:MapLayout, bounds:Rect):Void {
        mapItem.setBounds(bounds);
        if (!hasChildren()) return;
        var s:Number = sum();
        tiling.layout(this, bounds);
        for (var i=childCount()-1; i>=0; i--)
            getChild(i)._layout(tiling);
    }
    
    public function getTreeItems():Array {
        if (cachedTreeItems!=null){
            return cachedTreeItems;
		}
        var v:Array = new Array();
        addTreeItems(v);
        var m:Array = new Array[v.length];
        v.copyInto(m);
        cachedTreeItems = m;
        return m;
    }
    
    private function addTreeItems(v:Array):Void {
        if (!hasChildren())
            v.push(mapItem);
        else
            for (var i:Number=childCount()-1; i>=0; i--)
                getChild(i).addTreeItems(v);
    }
    
    private function sum():Number {
        if (!sumsChildren){
            return mapItem.getSize();
  	    }
        var s:Number = 0;
        for (var i=childCount()-1; i>=0; i--){
            s += getChild(i).sum();
		}
        mapItem.setSize(s);
        return s;
    }
    
    public function getItems():Array {
        if(childItems != null){
            return childItems;
		}
        var n:Number = childCount();
        childItems = new Array[n];
        for (var i=0; i<n; i++) {
            childItems[i] = getChild(i).getMapItem();
            childItems[i].setDepth(1+depth());
        }
        return childItems;
    }
    
    public function getMapItem():Mappable {
        return mapItem;
    }
    
    public function addChild(child:TreeModel):Void {
        child.setParent(this);
		children.push(child);
        childItems = null;
    }
    
    public function setParent(parent:TreeModel):Void {
        for (var p:TreeModel = parent; p != null; p = p.getParent()){
            if (p==this){
				throw new Error("Circular ancestry!");
			}
		}
        this.parent = parent;
    }
    
    public function getParent():TreeModel{
        return parent;
    }
    
    public function childCount():Number {
        return children.length;
    }
    
    public function getChild(n:Number):TreeModel {
		return TreeModel(children[n]);
    }
    
    public function hasChildren():Boolean {
        return children.length > 0;
    }
    
}